package com.jkgh.remedium.rsgen.generator.doc;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.*;

import com.jkgh.remedium.rsgen.vo.*;

public class RemediumSystemDocGenerator {

	private static final Set<String> PRIMITIVE_TYPES = new HashSet<String>(Arrays.asList("int", "byte", "double", "String"));

	public static void generate(File outputDir, RemediumSystem rs) throws FileNotFoundException {

		outputDir.mkdirs();
		deleteContents(outputDir);

		generateIndex(new File(outputDir, "index.html"), rs);

		for (DataObject d : rs.getDataObjects()) {
			RemediumDataObjectDocGenerator.generate(outputDir, d, rs);
		}

		for (MethodObject m : rs.getMethodObjects()) {
			RemediumMethodObjectDocGenerator.generate(outputDir, m, rs);
		}
	}

	private static void generateIndex(File systemOutput, RemediumSystem rs) throws FileNotFoundException {

		PrintWriter out = new PrintWriter(systemOutput);

		out.println("<html>");
		out.println("<head>");
		out.println("<title>" + rs.getName() + " Remedium System Documentation</title>");
		out.println("</head>");
		out.println("<body bgcolor=\"#eeeeee\">");

		List<String> problems = findProblems(rs);
		if (!problems.isEmpty()) {

			out.println("<h1>Problems:</h1>");

			out.println("<ul>");
			for (String p : problems) {
				out.println("<li>");
				out.println(p);
				out.println("</li>");
			}
			out.println("</ul>");
		} else {

			out.println("<h1>System name: " + rs.getName() + "</h1>");
			out.println("<h2>Source package: " + rs.getPackageName() + "</h2>");
			out.println("<h2>Access object: " + linkToClass(rs.getAccessClass()) + "</h2>");
			out.println("<h2>Objects:</h2>");
			out.println("<h3>Data objects:</h3>");
			for (DataObject d : rs.getDataObjects()) {
				out.println(linkToClass(d.getClassName()));
				out.println("<br>");
			}
			out.println("<br>");
			out.println("<h3>Method objects:</h3>");
			for (MethodObject m : rs.getMethodObjects()) {
				out.println(linkToClass(m.getClassName()));
				out.println("<br>");
			}
			out.println("</body>");
			out.println("</html>");
		}

		out.close();
	}

	private static List<String> findProblems(RemediumSystem rs) {

		List<String> ret = new ArrayList<String>();
		Set<String> checked = new HashSet<String>();

		for (DataObject d : rs.getDataObjects()) {
			if (!checked.add(d.getClassName())) {
				ret.add("Duplicated class <b>" + d.getClassName() + "</b>.");
			}
		}
		for (MethodObject m : rs.getMethodObjects()) {
			if (!checked.add(m.getClassName())) {
				ret.add("Duplicated class <b>" + m.getClassName() + "</b>.");
			}
		}

		for (DataObject d : rs.getDataObjects()) {
			checked.clear();
			for (FieldInfo fi : d.getFields()) {
				if (!checked.add(fi.getName())) {
					ret.add("Duplicated field <b>" + fi.getName() + "</b> in class <b>" + d.getClassName() + "</b>.");
				}
			}
		}

		for (MethodObject m : rs.getMethodObjects()) {
			checked.clear();
			for (MethodInfo mm : m.getMethods()) {
				if (!checked.add(mm.getName())) {
					ret.add("Duplicated method <b>" + mm.getName() + "</b> in class <b>" + m.getClassName() + "</b>.");
				}
				Set<String> argumentChecked = new HashSet<String>();
				for (Argument a : mm.getArguments()) {
					if (!argumentChecked.add(a.getName())) {
						ret.add("Duplicated argument name <b>" + a.getName() + "</b> in method <b>" + mm.getName() + "</b> of class <b>" + m.getClassName() + "</b>.");
					}
				}
			}
		}

		DataObject accessDataObject = findDataObject(rs, rs.getAccessClass());
		if (accessDataObject == null) {
			ret.add("Declared Access class <b>" + rs.getAccessClass() + "</b> not defined.");
		} else {

			Set<String> undefined = new HashSet<String>();
			Set<Object> visited = new HashSet<Object>();
			visit(rs, accessDataObject, visited, undefined);

			undefined.removeAll(PRIMITIVE_TYPES);
			for (String u : undefined) {
				ret.add("Declared class <b>" + u + "</b> not defined.");
			}

			Set<Object> all = new HashSet<Object>();
			all.addAll(rs.getDataObjects());
			all.addAll(rs.getMethodObjects());
			all.removeAll(visited);
			for (Object u : all) {
				ret.add("Defined class <b>" + findName(u) + "</b> not accessible.");
			}
		}

		return ret;
	}

	private static String findName(Object u) {

		if (u instanceof DataObject) {
			return ((DataObject) u).getClassName();
		}
		if (u instanceof MethodObject) {
			return ((MethodObject) u).getClassName();
		}
		return u.toString();
	}

	private static DataObject findDataObject(RemediumSystem rs, String className) {

		for (DataObject d : rs.getDataObjects()) {
			if (d.getClassName().equals(className)) {
				return d;
			}
		}
		return null;
	}

	private static void visit(RemediumSystem rs, DataObject d, Set<Object> visited, Set<String> undefined) {

		if (visited.add(d)) {
			for (FieldInfo f : d.getFields()) {
				String name = unArray(f.getClassName());
				DataObject sd = findDataObject(rs, name);
				if (sd != null) {
					visit(rs, sd, visited, undefined);
				} else {
					MethodObject sm = findMethodObject(rs, name);
					if (sm != null) {
						visit(rs, sm, visited, undefined);
					} else {
						undefined.add(name);
					}
				}
			}
		}
	}

	private static void visit(RemediumSystem rs, MethodObject m, Set<Object> visited, Set<String> undefined) {

		if (visited.add(m)) {
			for (MethodInfo mm : m.getMethods()) {

				List<String> names = new ArrayList<String>();
				for (Argument a : mm.getArguments()) {
					names.add(unArray(a.getClassName()));
				}
				if (mm.getReturnType() != null) {
					names.add(unArray(mm.getReturnType().getClassName()));
				}

				for (String name : names) {
					DataObject sd = findDataObject(rs, name);
					if (sd != null) {
						visit(rs, sd, visited, undefined);
					} else {
						MethodObject sm = findMethodObject(rs, name);
						if (sm != null) {
							visit(rs, sm, visited, undefined);
						} else {
							undefined.add(name);
						}
					}
				}
			}
		}
	}

	private static MethodObject findMethodObject(RemediumSystem rs, String className) {

		for (MethodObject m : rs.getMethodObjects()) {
			if (m.getClassName().equals(className)) {
				return m;
			}
		}
		return null;
	}

	public static String linkToClass(String className) {

		if (className.endsWith("[]")) {
			String sub = className.substring(0, className.length() - 2);
			if (PRIMITIVE_TYPES.contains(sub)) {
				return "<u>" + className + "</u>";
			} else {
				return "<a href=\"" + sub + ".html\">" + className + "</a>";
			}
		} else {
			if (PRIMITIVE_TYPES.contains(className)) {
				return "<u>" + className + "</u>";
			} else {
				return "<a href=\"" + className + ".html\">" + className + "</a>";
			}
		}
	}

	private static void deleteContents(File dir) {

		for (File f : dir.listFiles()) {
			if (f.isDirectory()) {
				deleteContents(f);
			}
			f.delete();
		}
	}

	public static String unArray(String className) {

		if (className.endsWith("[]")) {
			return className.substring(0, className.length() - 2);
		} else {
			return className;
		}
	}
}
